package com.qsw.qswcommonlib.util;

import android.graphics.PointF;

import com.qsw.qswcommonlib.util.math.LineEX;
import com.qsw.qswcommonlib.util.math.PointDEX;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * 数字相关工具
 */
public class CommonMathUtil {

    private CommonMathUtil() {
    }

    /**
     * 将每三个数字加上逗号处理，并保留两位小数（通常使用金额方面的编辑）
     *
     * @param money 金额
     * @return 处理完之后的字符串
     */
    public static String showMoney(double money) {
        DecimalFormat decimalFormat = new DecimalFormat(",##0.00");
        return decimalFormat.format(money);
    }

    /**
     * 将每三个数字加上逗号处理，并保留两位小数（通常使用金额方面的编辑）
     *
     * @param str 金额
     * @return 处理完之后的字符串
     */
    public static String showMoney(String str) {
        return showMoney(Double.parseDouble(str));
    }

    /**
     * 保留固定位数的小数，四舍五入
     *
     * @param source   原数据
     * @param decimals 要保留的位数
     * @return 转换后数据
     */
    public static BigDecimal transformDecimalOriginal(double source, int decimals) {
        return new BigDecimal(source).setScale(decimals, RoundingMode.HALF_DOWN);
    }

    /**
     * 保留两位小数，四舍五入
     *
     * @param source 原数据
     * @return 转换后数据
     */
    public static double transformTwoDecimal(double source) {
        return transformDecimalOriginal(source, 2).doubleValue();
    }

    /**
     * 保留固定位数的小数，四舍五入
     *
     * @param source   原数据
     * @param decimals 要保留的位数
     * @return 转换后数据
     */
    public static double transformDecimal(double source, int decimals) {
        // 以下两种方法实测结果一样
        // 实现方法1
//        return (Math.round(source * Math.pow(10, decimals)) / Math.pow(10, decimals));
        // 实现方法2
        return transformDecimalOriginal(source, decimals).doubleValue();
    }

    /**
     * 保留两位小数，四舍五入
     *
     * @param source 原数据
     * @return 转换后数据
     */
    public static String transformTwoDecimalString(double source) {
        return transformDecimalOriginal(source, 2).toString();
    }

    /**
     * 保留固定位数的小数，四舍五入
     *
     * @param source   原数据
     * @param decimals 要保留的位数
     * @return 转换后数据
     */
    public static String transformDecimalString(double source, int decimals) {
        return transformDecimalOriginal(source, decimals).toString();
    }

    /**
     * 判断一个点是否在多边形内
     *
     * @param point  要判断的点
     * @param srcPts 首位相连组成的多边形区域
     * @return true：在多边形内；false：在多边形外
     */
    public static boolean isPtInPoly(PointF point, List<PointF> srcPts) {
        if (srcPts == null || srcPts.isEmpty()) {
            return false;
        }
        List<PointF> pts;
        boolean needchange = false;
        boolean eastLongitude = srcPts.get(0).x >= 0;
        float minLongitude = srcPts.get(0).x;
        float maxLongitude = srcPts.get(0).x;
        for (PointF srcPt : srcPts) {
            minLongitude = Math.min(minLongitude, srcPt.x);
            maxLongitude = Math.max(maxLongitude, srcPt.x);
            if (!(eastLongitude && srcPt.x >= 0)) {
                needchange = true;
            }
        }
        needchange = needchange && (Math.abs(minLongitude) + Math.abs(maxLongitude) > 180);
        if (needchange) {
            pts = new ArrayList<>();
            for (PointF srcPt : srcPts) {
                if (srcPt.x < 0) {
                    pts.add(new PointF(360 + srcPt.x, srcPt.y));
                } else {
                    pts.add(srcPt);
                }
            }
        } else {
            pts = new ArrayList<>(srcPts);
        }

        int N = pts.size();
        boolean boundOrVertex = true; // 如果点位于多边形的顶点或边上，也算做点在多边形内，直接返回true
        int intersectCount = 0;// cross points count of x
        double precision = 2e-10; // 浮点类型计算时候与0比较时候的容差
        PointF p1, p2;// neighbour bound vertices
        PointF p = point; // 当前点

        p1 = pts.get(0);// left vertex
        for (int i = 1; i <= N; ++i) {// check all rays
            if (p.equals(p1)) {
                return boundOrVertex;// p is an vertex
            }

            p2 = pts.get(i % N);// right vertex
            if (p.x < Math.min(p1.x, p2.x) || p.x > Math.max(p1.x, p2.x)) {// ray is outside of our interests
                p1 = p2;
                continue;// next ray left point
            }

            if (p.x > Math.min(p1.x, p2.x) && p.x < Math.max(p1.x, p2.x)) {// ray is crossing over by the algorithm (common part of)
                if (p.y <= Math.max(p1.y, p2.y)) {// x is before of ray
                    if (p1.x == p2.x && p.y >= Math.min(p1.y, p2.y)) {// overlies on a horizontal ray
                        return boundOrVertex;
                    }

                    if (p1.y == p2.y) {// ray is vertical
                        if (p1.y == p.y) {// overlies on a vertical ray
                            return boundOrVertex;
                        } else {// before ray
                            ++intersectCount;
                        }
                    } else {// cross point on the left side
                        double xinters = (p.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;// cross point of y
                        if (Math.abs(p.y - xinters) < precision) {// overlies on a ray
                            return boundOrVertex;
                        }

                        if (p.y < xinters) {// before ray
                            ++intersectCount;
                        }
                    }
                }
            } else {// special case when ray is crossing through the vertex
                if (p.x == p2.x && p.y <= p2.y) {// p crossing over p2
                    PointF p3 = pts.get((i + 1) % N); // next vertex
                    if (p.x >= Math.min(p1.x, p3.x) && p.x <= Math.max(p1.x, p3.x)) {// p.x lies between p1.x & p3.x
                        ++intersectCount;
                    } else {
                        intersectCount += 2;
                    }
                }
            }
            p1 = p2;// next ray left point
        }

        if (intersectCount % 2 == 0) {// 偶数在多边形外
            return false;
        } else { // 奇数在多边形内
            return true;
        }
    }

    /**
     * 计算角AOB的角度，范围：0-180
     *
     * @param pointO 角O
     * @param pointA 点A
     * @param pointB 点B
     * @return 角AOB的角度
     */
    public static double calAngle(PointDEX pointO, PointDEX pointA, PointDEX pointB) {
        return calAngle(pointO.getX(), pointO.getY(), pointA.getX(), pointA.getY(), pointB.getX(), pointB.getY());
    }

    /**
     * 计算角AOB的角度，范围：0-180
     *
     * @param oX 角O X坐标
     * @param oY 角O Y坐标
     * @param aX 点A X坐标
     * @param aY 点A Y坐标
     * @param bX 点B X坐标
     * @param bY 点B Y坐标
     * @return 角AOB的角度
     */
    public static double calAngle(double oX, double oY, double aX, double aY, double bX, double bY) {
        // 向量的点乘
        double t = (aX - oX) * (bX - oX) + (aY - oY) * (bY - oY);

        // 为了精确直接使用而不使用中间变量
        // 包含了步骤：A=向量的点乘/向量的模相乘
        //          B=arccos(A)，用反余弦求出弧度
        //          result=180*B/π 弧度转角度制
        return 180 * Math.acos(
                t / Math.sqrt((Math.abs((aX - oX) * (aX - oX)) + Math.abs((aY - oY) * (aY - oY)))
                        * (Math.abs((bX - oX) * (bX - oX)) + Math.abs((bY - oY) * (bY - oY))))
        ) / Math.PI;
    }

    /**
     * 计算点到线段的最短距离。当点到直线的垂足在线段中时，为垂线长度，当垂足在线段外时，为点到线段两端点距离的较小值。
     *
     * @param point             点
     * @param lineSegmentPoint1 线段点1
     * @param lineSegmentPoint2 线段点2
     * @return 最短距离
     */
    public static double calPointToLineSegmentMinDistance(PointDEX point, PointDEX lineSegmentPoint1, PointDEX lineSegmentPoint2) {
        return calPointToLineSegmentMinDistance(point.getX(), point.getY(), lineSegmentPoint1.getX(), lineSegmentPoint1.getY(), lineSegmentPoint2.getX(), lineSegmentPoint2.getY());
    }

    /**
     * 计算点到线段的最短距离。当点到直线的垂足在线段中时，为垂线长度，当垂足在线段外时，为点到线段两端点距离的较小值。
     *
     * @param pointX      点X
     * @param pointY      点Y
     * @param linePoint1X 线段点1X
     * @param linePoint1Y 线段点1Y
     * @param linePoint2X 线段点2X
     * @param linePoint2Y 线段点2Y
     * @return 最短距离
     */
    public static double calPointToLineSegmentMinDistance(double pointX, double pointY, double linePoint1X, double linePoint1Y, double linePoint2X, double linePoint2Y) {
        double minDistance;
        double degrees1 = calAngle(linePoint1X, linePoint1Y, pointX, pointY, linePoint2X, linePoint2Y);
        double degrees2 = calAngle(linePoint2X, linePoint2Y, pointX, pointY, linePoint1X, linePoint1Y);
        if (degrees1 < 90 && degrees2 < 90) {
            // 点到线的垂足在线段上，最小距离为垂线长
            LineEX lineSegment = new LineEX(linePoint1X, linePoint1Y, linePoint2X, linePoint2Y);
            minDistance = lineSegment.getDistance(pointX, pointY);
        } else {
            // 点到线的垂足不在线段上，最小距离为点到线段两端点的较小者
            double distance1 = Math.sqrt(Math.pow(pointX - linePoint1X, 2) + Math.pow(pointY - linePoint1Y, 2));
            double distance2 = Math.sqrt(Math.pow(pointX - linePoint2X, 2) + Math.pow(pointY - linePoint2Y, 2));
            minDistance = Math.min(distance1, distance2);
        }
        return minDistance;
    }

    /**
     * 计算点到线段的垂线长
     *
     * @param point             点
     * @param lineSegmentPoint1 线段点1
     * @param lineSegmentPoint2 线段点2
     * @return 点到线段的垂线长
     */
    public static double calPointToLineSegmentVerticalDistance(PointDEX point, PointDEX lineSegmentPoint1, PointDEX lineSegmentPoint2) {
        return calPointToLineSegmentVerticalDistance(point.getX(), point.getY(), lineSegmentPoint1.getX(), lineSegmentPoint1.getY(), lineSegmentPoint2.getX(), lineSegmentPoint2.getY());
    }

    /**
     * 计算点到线段的垂线长
     *
     * @param pointX      点X
     * @param pointY      点Y
     * @param linePoint1X 线段点1X
     * @param linePoint1Y 线段点1Y
     * @param linePoint2X 线段点2X
     * @param linePoint2Y 线段点2Y
     * @return 点到线段的垂线长
     */
    public static double calPointToLineSegmentVerticalDistance(double pointX, double pointY, double linePoint1X, double linePoint1Y, double linePoint2X, double linePoint2Y) {
        LineEX lineSegment = new LineEX(linePoint1X, linePoint1Y, linePoint2X, linePoint2Y);
        return lineSegment.getDistance(pointX, pointY);
    }

    /**
     * 计算两直线交点
     *
     * @param line1Point1 直线1点1
     * @param line1Point2 直线1点2
     * @param line2Point1 直线2点1
     * @param line2Point2 直线2点2
     * @return 两直线交点，若两直线平行则返回null
     */
    public static PointDEX calLineIntersectionPoint(PointDEX line1Point1, PointDEX line1Point2, PointDEX line2Point1, PointDEX line2Point2) {
        return calLineIntersectionPoint(line1Point1.getX(), line1Point1.getY(), line1Point2.getX(), line1Point2.getY(), line2Point1.getX(), line2Point1.getY(), line2Point2.getX(), line2Point2.getY());
    }

    /**
     * 计算两直线交点
     *
     * @param line1Point1X 线段1点1X
     * @param line1Point1Y 线段1点1Y
     * @param line1Point2X 线段1点2X
     * @param line1Point2Y 线段1点2Y
     * @param line2Point1X 线段2点1X
     * @param line2Point1Y 线段2点1Y
     * @param line2Point2X 线段2点2X
     * @param line2Point2Y 线段2点2Y
     * @return 两直线交点，若两直线平行则返回null
     */
    public static PointDEX calLineIntersectionPoint(double line1Point1X, double line1Point1Y, double line1Point2X, double line1Point2Y,
                                                    double line2Point1X, double line2Point1Y, double line2Point2X, double line2Point2Y) {
        LineEX line1 = new LineEX(line1Point1X, line1Point1Y, line1Point2X, line1Point2Y);
        LineEX line2 = new LineEX(line2Point1X, line2Point1Y, line2Point2X, line2Point2Y);
        return line1.getIntersection(line2);
    }

    /**
     * 计算两个角的夹角，范围0-180
     *
     * @param angle1 角度1
     * @param angle2 角度2
     * @return 0-180
     */
    private static double calIncludedAngle(double angle1, double angle2) {
        if (angle1 * angle2 >= 0) {
            return Math.abs(angle1 - angle2);
        } else {
            double a = Math.abs(angle1 - angle2);
            if (a > 180) {
                a = 360 - a;
            }
            return a;
        }
    }

    /**
     * 将弧度转换为0-360的角度
     *
     * @param radian 弧度
     * @return 转换后的角度
     */
    public static double transformRadianTo360(double radian) {
        return transformDegreesTo360(Math.toDegrees(radian));
    }

    /**
     * 将角度转换为0-360的角度
     *
     * @param degrees 角度
     * @return 转换后的角度
     */
    public static double transformDegreesTo360(double degrees) {
        while (degrees >= 360) {
            degrees -= 360;
        }
        while (degrees < 0) {
            degrees += 360;
        }
        return degrees;
    }

    /**
     * 将角度转换为-180-180范围内
     *
     * @param angle 任意角度
     * @return -180-180范围内的角度
     */
    private static double transformDegreesTo180(double angle) {
        while (angle > 180) {
            angle -= 360;
        }
        while (angle < -180) {
            angle += 360;
        }
        return angle;
    }

    /**
     * 根据风速获取风级
     *
     * @param ws 风速，单位：m/s
     * @return 风级，0-12
     */
    public static byte getWindLevel(float ws) {
        if (ws < 0.3) {
            return 0;
        } else if (ws < 1.6) {
            return 1;
        } else if (ws < 3.4) {
            return 2;
        } else if (ws < 5.5) {
            return 3;
        } else if (ws < 8.0) {
            return 4;
        } else if (ws < 10.8) {
            return 5;
        } else if (ws < 13.9) {
            return 6;
        } else if (ws < 17.2) {
            return 7;
        } else if (ws < 20.8) {
            return 8;
        } else if (ws < 24.5) {
            return 9;
        } else if (ws < 28.5) {
            return 10;
        } else if (ws < 32.5) {
            return 11;
        } else {
            return 12;
        }
    }
}
